package org.fhnw.aigs.client.communication; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.Socket; import java.net.URL; import java.net.UnknownHostException; import java.util.logging.Level; import java.util.logging.Logger; import org.fhnw.aigs.client.GUI.SettingsWindow; import org.fhnw.aigs.client.gameHandling.ClientGame; import org.fhnw.aigs.commons.communication.IdentificationMessage; import org.fhnw.aigs.commons.communication.IdentificationResponseMessage; /** * This class is responsible for everything related to the network communication * to the server. Due to the fact that the class uses the Singleton Pattern, it * is not possible to instantiate ClientCommunication directly. In order to get * to an instance, use <b>getInstance()</b> instead.<br> * v1.0 Initial release<br> * v1.1 Functional changes<br> * v1.1.1 Minor changes due to changes in other clssses (dependencies) * * @author Matthias Stöckli (v1.0) * @version v1.1.1 (Raphael Stoeckli, 22.10.2014) */ public class ClientCommunication implements Runnable { /** * The port number which is used for communication with the server. It must * be an integer between 0 and 65535. */ private int port; /** * The host address. This is usually the external IP address of the server * or, if developping locally, "localhost". */ private String host; /** * The Socket, i.e. the connection to the server. */ private Socket socket; /** * A flag which indicates whether a connection has been established. */ public static boolean isConnected; /** * A flag which indicates whether the client communication has been set * using the online configuration file. See * {@link ClientCommunication#setCredentialsUsingOnlineConfiguration}. */ //public static boolean hasReadFromFile; /** * The client game which will be used to initialize the * {@link ClientMessageBroker}. */ private ClientGame clientGame; /** * Private constructor to prevent instantiation */ private ClientCommunication() { } /** * Gets the only instance of ClientCommunication. Synchronized indicates * that this Singleton is thread-safe. * * @return The ServerCommunication instance */ public static synchronized ClientCommunication getInstance() { return ClientCommunication.ClientCommunicationHolder.INSTANCE; } /** * Provides a container for the ServerCommunication singleton */ private static final class ClientCommunicationHolder { private static final ClientCommunication INSTANCE = new ClientCommunication(); } /** * See {@link ClientCommunication#port}. */ public int getPort() { return port; } /** * See {@link ClientCommunication#host}. */ public String getHost() { return host; } /** * See {@link ClientCommunication#clientGame}. */ public ClientGame getClientGame() { return clientGame; } /** * See {@link ClientCommunication#socket} */ public Socket getSocket() { return socket; } /** * See {@link ClientCommunication#port}. */ public void setPort(int port) { this.port = port; } /** * See {@link ClientCommunication#host}. */ public void setHost(String host) { this.host = host; } /** * See {@link ClientCommunication#clientGame}. */ public void setClientGame(ClientGame clientGame) { this.clientGame = clientGame; } /** * Usually there is no need to use this method as the connection process * takes place automatically. See {@link ClientCommunication#socket}. */ public void setClientGame(Socket socket) { this.socket = socket; } /** * This method must be called prior to connection to the server.<br> * It sets the clientGame, the host (IP address or localhost) and the port * (e.g. 25123). * * @param clientGame The clientGame. * @param host The host to which the client will establish a connection. It * can either be a valid IP addreess or localhost * @param port A valid port number between */ public static void setCredentials(ClientGame clientGame, String host, int port) { ClientCommunication instance = getInstance(); instance.setPort(port); instance.setHost(host); instance.setClientGame(clientGame); } /** * This is an alternative for the {@link ClientCommunication#setCredentials(org.fhnw.aigs.client.gameHandling.ClientGame, java.lang.String, int) } * This method downloads the credentials (port and address) from a * predefined, permanent url. This should make sure that the port and host * are always up to date * @deprecated Don't use this method in the future, respectively make it configurable * @param clientGame The ClientGame instance */ public static void setCredentialsUsingOnlineConfiguration(ClientGame clientGame) { // CHANGE TO THE ACTUAL LOCATION OF THE FILE CONTAINING THE STANDARD CREDENTIALS. String urlString = "http://www.poebel.ch/aigs/aigs.txt"; ClientCommunication instance = getInstance(); instance.setClientGame(clientGame); try { URL url = new URL(urlString); Logger .getLogger(ClientCommunication.class .getName()).log(Level.INFO, "Downloading settings from URL:{0}", urlString); BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8")); // Check for correct connection: In some cases only limited connection is available // e.g. when not properly logged in into the FHNW network instance.setHost(reader.readLine()); instance.setPort(Integer.parseInt(reader.readLine())); Logger.getLogger(ClientCommunication.class.getName()).log(Level.INFO, "Loaded the following settings: {0}:{1}", new Object[]{instance.getHost(), instance.getPort()}); } catch (MalformedURLException ex) { Logger.getLogger(ClientCommunication.class.getName()).log(Level.SEVERE, "URL is malformed.", ex); } catch (IOException ex) { // If the settings could not be read, use localhost and a standard port instead. Logger.getLogger(ClientCommunication.class.getName()).log(Level.SEVERE, "Could not load settings from " + urlString + "" + "Please check your internet connection. Meanwhile localhost and the port 25123 will be used.", ex); instance.setPort(25123); instance.setHost("localhost"); } catch (Exception ex) // All orher exceptions { Logger.getLogger(ClientCommunication.class.getName()).log(Level.SEVERE, "An unknown Error occurred.", ex); } } /** * Starts the method {@link ClientCommunication#establishConnection}. */ @Override public void run() { establishConnection(); } /** * Establishes a connection to the server. A {@link Socket} is created in * order to connect to the server. If this is not possible, the client waits * for 5 seconds and retries again. As soon as the connection has been * successfully established, a new message loop will be started. * */ private void establishConnection() { do { try { // Open a new socket to the specified host/port. socket = new Socket(host, port); isConnected = true; Logger.getLogger(ClientCommunication.class.getName()).log(Level.INFO, "Connection to server established!"); // Create a new ClientMessageBroker which is responsible for // all messaging-related routig and parsing. Create a thread and // start it. ClientMessageBroker clientMessageBroker = new ClientMessageBroker(socket, clientGame); Thread clientMessageBrokerThread = new Thread(clientMessageBroker); clientMessageBrokerThread.setName("ClientMessageBrokerThread"); clientMessageBrokerThread.start(); // Check the user's identity. checkIdentity(); } catch (UnknownHostException ex) { Logger.getLogger(ClientCommunication.class .getName()).log(Level.SEVERE, "Do not know about host: {0}", host); System.err.println( "Retrying in 5 Seconds..."); } // This exception is very common. It means that no connection could // be established. In most cases this simply means that the server // is not running. catch (IOException ex) { Logger.getLogger(ClientCommunication.class .getName()).log(Level.SEVERE, "Could not get I/O: {0}: {1}", new Object[]{host, port}); System.err.println( "Retrying in 5 Seconds..."); } // Wait 5 seconds (5000 ms) and then try to reconnect. try { Thread.sleep(5000); } catch (InterruptedException ex) { Logger.getLogger(ClientCommunication.class .getName()).log(Level.SEVERE, null, ex); } } while (socket == null && isConnected == false); } /** * This method looks whether an instance of the Setting class existing. * If not available, the settings window will be showed.<br> * The user name and identifivcation stored in the settings will be * sent as an identification to the server * via a {@link IdentificationMessage}.<br> * If the name and the identification code match and the user is currently * not logged in the system, the identification is successful. The server * will send an {@link IdentificationResponseMessage} either way, informing * the client about success of failure of the log-in attempt. <br> * If the log in failed, the {@link SettingsWindow} will be shown to the * user. */ private void checkIdentity() { if (Settings.getInstance().isInitialized() == false) { Settings.tryLoadSettings(true); // Opens the settings window if no settings available } // Sends an identification to the Server over the new connection IdentificationMessage identificationMessage = new IdentificationMessage(Settings.getInstance().getUsername(), Settings.getInstance().getPassword(),Settings.getInstance().getDisplayname()); clientGame.sendMessageToServer(identificationMessage); Logger.getLogger(ClientCommunication.class.getName()).log(Level.INFO, "Sent identification!"); } }